/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ThreadBasedSimulator;

import java.io.File;
import java.io.FileInputStream;
import java.util.StringTokenizer;

/**
 *
 * @author mahesh
 */
public class ProcessorThread extends Thread {
    
    public ProcessorThread()
    {
        m_nR = new int[4];
        PS_BOOTING_UP           = 0;
        PS_INSTRUCTION_FETCH    = 1;
        PS_INSTRUCTION_DECODE   = 2;
        PS_INSTRUCTION_EXECUTE  = 3;
    }
    public ProcessorThread( Clock clk, MemoryThread memory, IOThread io )
    {
        m_clock = clk;    
        m_Memory = memory;
        m_IO = io;
        m_nR = new int[4];
        m_nPC = 64;
        
        PS_BOOTING_UP           = 0;
        PS_INSTRUCTION_FETCH    = 1;
        PS_INSTRUCTION_DECODE   = 2;
        PS_INSTRUCTION_EXECUTE  = 3;
    }
    public ProcessorThread( Clock clk, String sBootupFile )
    {
        m_clock = clk;    
        m_sBootupProgramFile = sBootupFile;
                
        m_nR = new int[4];
        m_nPC = 64;
        
        PS_BOOTING_UP           = 0;
        PS_INSTRUCTION_FETCH    = 1;
        PS_INSTRUCTION_DECODE   = 2;
        PS_INSTRUCTION_EXECUTE  = 3;
    }
    void SetBootupFile( String sBootupFile )
    {
        m_sBootupProgramFile = sBootupFile;
    }
    void SetMemory( MemoryThread memory )
    {
        m_Memory = memory;
    }
    
    ///////////////////////////////////////////////////////////
    // The bootup program that executes once when processor starts
    // The file is assumed to be in m_sBootupProgramFile
    // All this function does is to pick out all the machine code in 
    // that file and load it to memory
    ///////////////////////////////////////////////////////////
    private void LoadBootupProgram()
    {
        // set the processor state first, so that the UI can display it
        m_nProcessorState = ProcessorThread.PS_BOOTING_UP;
        
        
        // Assuming that the machine code is stored as binary strings
        // delimited by a newline        
        
        // First, read the code from the disk
        // We do not wait for the clock signal for this case
        try
        {
            System.err.println(" file name = " + m_sBootupProgramFile );
            File file = new File( m_sBootupProgramFile );
            FileInputStream fin = new FileInputStream( file );
            
            byte [] data = new byte[ (int)file.length() ];
            fin.read(data);
            
            String sMachineCode = new String( data );            
            StringTokenizer st = new StringTokenizer( sMachineCode, "\n" );
            System.err.println( " Num instructions = " + st.countTokens() );
            
            
            int nCurrentAddress = 16;
            // we are starting off at 64 since we consider the initial 
            // addresses to be reserved
            while( st.hasMoreTokens() )
            {
                String sInstr   = st.nextToken();
                sInstr = sInstr.trim();
                System.err.println( " sInstr = " + sInstr );
                // note that this is a binary string.
                // convert it to an integer before storing it to memory
                int nInstr      = Integer.parseInt(sInstr, 2);
                m_Memory.WriteUnsynchronized(nInstr, nCurrentAddress);
                
                nCurrentAddress += 2;
            }
            
            // At this stage, the program has been loaded 
            // Perform initialization of the various registers
            m_nR = new int[4];
            
        }
        catch( Exception ex )
        {
            System.out.println( "Exception at bootup : "+ ex );
            System.exit(0);
        }
    }
    ///////////////////////////////////////////////////////////
    // The instruction loop for the processor. There are 5 steps
    // 1. Fetch Instruction from memory ( needs one clock cycle )
    // 2. Decode Instruction
    // 3. Fetch Operands ( needs one clock cycle )
    // 4. Execute Instruction ( needs one clock cycle )
    // 5. Store Results ( needs one clock cycle )
    ///////////////////////////////////////////////////////////
    private void InstructionLoop()
    {
        if( !m_bStop )
        {
            Fetch();
            
            WaitForStepCommand();
            Decode();
            
            WaitForStepCommand();
            FetchOperands();
            
            WaitForStepCommand();
            ExecuteInstruction();            
        }
    }
    
    private void Fetch()
    {
        // set the processor state first, so that the UI can display it
        m_nProcessorState = ProcessorThread.PS_INSTRUCTION_FETCH;
        
        // check to see if the trap flag has been set. 
        //If yes, save state and jump to appropriate location
        // The address of the next instruction is in PC
        m_nIR = ReadWordFromMemory( m_nPC );
        System.err.println(" ProcessorThread, Fetch : " + m_nIR + " " + m_nPC );
        m_nPC += 2;
    }
    private void Decode() 
    {
        // set the processor state first, so that the UI can display it
        m_nProcessorState = ProcessorThread.PS_INSTRUCTION_DECODE;
        
        
        /* This function takes a opcode in IR as input (e.g Add, Sub etc) and returns the corresponding instruction format
         * Format 1 : Special (Used for instructions that require no operands, eg HLT etc)
         * Format 2 : 1 Register operand (eg. TRAP instruction)
         * Format 3 : 2 Register operands(eg. OR, AND instructions )
         * Format 4 : 2 Registers and one offset (eg. LDR, STR instructions)
         * Format 5 : 1 Register and 1 Immediate (eg. SIR, AIR instructions)
         * Format 6 : Only immediate operand     (eg. RFS instruction)
         * Format 7 : I/O instruction (eg. IN, OUT )
         * Format 8 : Shift/ Rotate instructions (eg. SRC, RRC )
         * Format 9 : Using index register
         */
        m_nCC       = 0;              // Clear the condition code register
        m_nOpcode   = m_nIR / 1024;
        int format  = -1;
        
        
        switch( m_nOpcode )
        {
            case 000: 
                format = 1; break;
            case 025: case 030:
                format = 2; break;
            case 020: case 021: case 022: case 023: case 024:
                format = 3; break;
            case 004 : case 005: case 010: case 011: case 016: case 001:
            case 002: case 003:
                format = 4; break;
            case 006: case 007:
                format = 5; break;
            case 015:
                format = 6; break;
            case 061: case 062: case 063:
                format = 7; break;
            case 031: case 032:
                format = 8; break;
            case 041: case 042: case 013: case 014:
                format = 9; break;
            default:
                format = -1;
                break;  // we need to set up a machine fault for this case
        }
        
        m_nInstructionFormat = format;        
        System.err.println(" ProcessorThread, Decode : " + m_nInstructionFormat );
    }
    
    private void FetchOperands() 
    {
        // Based on the format, we need to decide where to get the operands from
        // We can get operands in registers in the same clock cycle
        // But we need to wait for a clock cycle to get data from memory
        int temp = m_nIR % 1024;
        switch( m_nInstructionFormat )
        {
            case 1:
                // there are no operands for this case. so we can just skip
                break;
            case 2:
                // one register operand
                // assumed to be in bits 6,7                
                m_nR1       = temp / 256;
                m_nOperand1 = m_nR[ m_nR1 ];
                break;
            case 3:
                // two register operands                
                m_nR1   = temp / 256;
                temp    = temp % 256;
                m_nR2   = temp / 64;
                m_nOperand1 = m_nR[ m_nR1 ];
                m_nOperand2 = m_nR[ m_nR2 ];
                break;
            case 4:
                // two registers and one offset                
                if( temp / 512 == 0)
                    m_bIndirectMode = false;
                else
                    m_bIndirectMode = true;
                
                temp = temp % 512;
                
                if( temp / 256 == 0)
                    m_bIndexMode = false;
                else
                    m_bIndexMode = true;
                
                
                temp    = temp % 256;
                m_nR1   = temp / 64;
                
                m_nOffset = temp % 64;
                
                break;
            case 5:
                // one register and one immediate
                temp            = temp % 256;
                m_nR1           = temp / 64;
                m_nImmediate    = temp % 64;
                
                break;
            case 6:
                // one immediate
                m_nImmediate    = temp % 64;
                break;
            case 7:
                // IO
                temp            = temp % 512;
                m_nR1           = temp / 128;
                m_nDevId        = temp % 64;
                
                break;
            case 8:
                // Shift
                break;
            case 9:
                // Uses Index register
                temp        = temp % 512;
                if( temp / 256 == 0)
                    m_bIndexMode = false;
                else
                    m_bIndexMode = true;
                
                m_nOffset   = temp % 64;
                break;
        }
        System.err.println("ProcessorThread, FetchOperands : operands = " + m_nR1 + " " + m_nR2 +" " + m_bIndexMode + " " + m_bIndirectMode );
    }

    private void ExecuteInstruction() 
    {
        // set the processor state first, so that the UI can display it
        m_nProcessorState = ProcessorThread.PS_INSTRUCTION_EXECUTE;
        
        System.err.println( "ExedcuteInstruction, opcode :" + m_nOpcode);
        switch( m_nOpcode )
        {
            case 000: 
            // HLT instruction
            m_bStop = true;  break;
            ///////////////// Single Operand Instr /////////////////////////    
            case 025:
                //NOT instruction  
                m_nR[ m_nR1 ] = (~m_nR[ m_nR1 ]) % 65536; // the modulus is to ensure the upper bits are not inverted
                break;
            case 030:
                //TRAP
                System.err.println("Executing trap, registers contain :" + m_nR[0]+ " " + m_nR[1] + " " + m_nR[2] );
                // convention that for trap, m_nR1 == 0
                if( m_nR[ 0 ] == 4 )
                {
                    // print string whose address is in R2, and length is in R3
                    // here printing = copying to IO output buffer
                    
                    String sTempStr = ReadBlockFromMemory(m_nR[1], m_nR[2]);
                    m_IO.SetScreenData(sTempStr);
                }
                else if( m_nR[ m_nR1 ] == 5 )
                {
                    // print integer
                }
                else if( m_nR[ m_nR1 ] == 6 )
                {
                    // read string
                }
                else if( m_nR[ m_nR1 ] == 7 )
                {
                    // read integer
                }
                break;
            ///////////////// Two Operand Instr /////////////////////////
            case 020: 
                // MUL
                // the operands should be in regsiters 0 or 2
                if( m_nR1 % 2 == 0 && m_nR2 % 2 == 0 )
                {
                    int temp            = m_nR[ m_nR1 ] * m_nR[ m_nR2 ];
                    m_nR[ m_nR1 ]       = temp / 65536;
                    m_nR[ m_nR1 + 1 ]   = temp % 65536;
                }
                break;
            case 021: 
                // DIV
                // the operands should be in regsiters 0 or 2
                if( m_nR1 % 2 == 0 && m_nR2 % 2 == 0 )
                {
                    int temp            = m_nR[ m_nR1 ] / m_nR[ m_nR2 ];
                    m_nR[ m_nR1 ]       = temp / 65536;
                    m_nR[ m_nR1 + 1 ]   = temp % 65536;
                }
                break;
                
            case 022: 
                // TST 
                if( m_nR[ m_nR1 ] == m_nR[ m_nR2 ])
                    m_nCC += 8;                 // set the equal flag to true
                // the cc register is assumed to be reset every instruction
                // hence not setting it to zero explicitly
                break;
                
            case 023: 
                // AND
                m_nR[ m_nR1 ] = m_nR[ m_nR1 ] & m_nR[ m_nR2 ];
                break;
            case 024:
                // OR
                m_nR[ m_nR1 ] = m_nR[ m_nR1 ] | m_nR[ m_nR2 ];
                break;
                
            ///////////////// offset based Operand Instr /////////////////////////
            case 004 : 
                // ADD
                int nEffectiveAddress = GetEffectiveAddress();
                
                // Now that we have the effective address, let us add
                m_nR[ m_nR1 ] += ReadByteFromMemory(nEffectiveAddress);
                
                break;
            case 005: 
                // SUB
                nEffectiveAddress = GetEffectiveAddress();
                
                // Now that we have the effective address, let us sub
                m_nR[ m_nR1 ] -= ReadByteFromMemory(nEffectiveAddress);
                break;
                
            case 010: 
                // JZ
                nEffectiveAddress = GetEffectiveAddress();
                
                if( m_nR[ m_nR1 ] == 0 )
                    m_nPC = nEffectiveAddress;
                
                break;
                
            case 011: 
                // JZ
                nEffectiveAddress = GetEffectiveAddress();
                
                if( m_nR[ m_nR1 ] != 0 )
                    m_nPC = nEffectiveAddress;
                
                break;
            case 016:
                // SOB : my favorite :)
                nEffectiveAddress = GetEffectiveAddress();
                m_nR[ m_nR1 ]--;
                if( m_nR[ m_nR1 ] > 0 )
                    m_nPC = nEffectiveAddress;
                
                break;
                
            case 001:
                // LDR
                nEffectiveAddress = GetEffectiveAddress();
                System.err.println( " ProcessorThread, ExecuteInstruction : effective Addr = " + nEffectiveAddress );
                m_nR[ m_nR1 ] = ReadWordFromMemory(nEffectiveAddress);
                System.err.println(" Content after reading from memory = " + m_nR[ m_nR1 ]);
                break;
                
            case 002: 
                // STR
                nEffectiveAddress = GetEffectiveAddress();
                
                WriteWordToMemory(nEffectiveAddress, m_nR[ m_nR1 ]);                
                break;

            case 003:
                // LDA
                nEffectiveAddress = GetEffectiveAddress();
                
                m_nR[ m_nR1 ] = nEffectiveAddress;
                break;
                
            /////////////////// Immediate instr ///////////////////////////
            case 006: 
                // AIR
                m_nR[ m_nR1 ] += m_nImmediate;
                break;
            case 007:
                // SIR
                m_nR[ m_nR1 ] -= m_nImmediate;
                break;
                
            case 015:
                // RFS... Skipping JFS and RFS for now
                break;
                
            case 061: 
                // IN
                //m_IO.SetOperation(IOThread.FETCH);
                //m_nR[ m_nR1 ] = m_nDeviceRegisters[ m_nDevId ];                
                break;
            case 062: 
                // OUT
                
                break;
            case 063:
                // CHK
                break;
                
            case 031: case 032:
                break;
            case 041: 
                // LDX
                nEffectiveAddress   = GetEffectiveAddress();
                System.err.println( " ProcessorThread, ExecuteInstruction : effective Addr = " + nEffectiveAddress );
                m_nX0                = ReadWordFromMemory(nEffectiveAddress);
                break;
            case 042: 
                // STX
                nEffectiveAddress = GetEffectiveAddress();
                System.err.println( " ProcessorThread, ExecuteInstruction : effective Addr = " + nEffectiveAddress );
                WriteWordToMemory( nEffectiveAddress, m_nX0 ); 
                break;
                
            case 013: case 014:
                break;
            default:                
                break;  // we need to set up a machine fault for this case
        }
    }

    
    
    private int GetEffectiveAddress()
    {
        int nEffectiveAddress = 0;
        System.err.println(" m_bIndexMode = " + m_bIndexMode + " , " + m_bIndirectMode + ", offset = " + m_nOffset );
                if( m_bIndexMode )
                    nEffectiveAddress += m_nX0;
                nEffectiveAddress += m_nOffset;
                
                if( m_bIndirectMode )
                {
                    // This means we have to load this from the memory
                    nEffectiveAddress = ReadWordFromMemory(nEffectiveAddress);
                }
                
                return nEffectiveAddress;
    }
    ///////////////////////////////////////////////////////////
    // Functions to access memory
    ///////////////////////////////////////////////////////////
    int ReadWordFromMemory( int nAddr )
    {
        m_Memory.SetMAR(nAddr);
        m_Memory.SetOperation(MemoryThread.READ_WORD);
        WaitForClock();
        WaitForClock();
        
        if( m_Memory.HasCompleted() )
            return m_Memory.GetMDR();
        else return -1;
    }
    
    int ReadByteFromMemory( int nAddr )
    {
        m_Memory.SetMAR(nAddr);
        m_Memory.SetOperation(MemoryThread.READ_BYTE);
        WaitForClock();
        WaitForClock();
        // we wait for two clocks to ensure that the memory has completed the operation
        if( m_Memory.HasCompleted() )
            return m_Memory.GetMDR();
        else return -1;
    }
    
    String ReadBlockFromMemory( int nAddr, int length )
    {
        m_Memory.SetMAR(nAddr);
        m_Memory.SetBlockSize( length );
        m_Memory.SetOperation(MemoryThread.READ_BLOCK);
        
        WaitForClock();
        WaitForClock();
        
        if( m_Memory.HasCompleted() )
            return m_Memory.GetBlockBuffer();
        else return "";
    }
    void WriteWordToMemory( int nAddr, int nVal )
    {
        m_Memory.SetMAR(nAddr);
        m_Memory.SetMDR(nVal);
        m_Memory.SetOperation(MemoryThread.WRITE_WORD);
        WaitForClock();
        WaitForClock();
        
    }
    void WriteByteToMemory( int nAddr, int nVal )
    {
        m_Memory.SetMAR(nAddr);
        m_Memory.SetMDR(nVal);
        m_Memory.SetOperation(MemoryThread.WRITE_BYTE);
        WaitForClock();
        WaitForClock();
    }
    ///////////////////////////////////////////////////////////
    // The main thread for the Processor
    ///////////////////////////////////////////////////////////
    @Override
    @SuppressWarnings("empty-statement")
    public void run() {
        
        
        while( true )
        {       
            WaitForStepCommand();
            WaitForClock();
            
            // at this point we have the clock's permission to go on
            if( m_bHasBootedUp == false )
            {
                // This means we have not yet booted up
                // STEP 1 : Load the bootup program.
                // We need to wait for the first clock signal to be sure that 
                // the memory has been initialized                
                LoadBootupProgram();
                m_bHasBootedUp = true;
            }
            else
            {
                InstructionLoop();
                if( m_bStop == true )
                    break;
                else if( m_bDebugMode )
                    m_bStepForward = false;
            }
                
        }
    }
    
    @SuppressWarnings("empty-statement")
    void WaitForStepCommand()
    {
        if( m_bDebugMode )
            {
                // first clear the step forward flag
                m_bStepForward = false;
                // wait till you are asked to step forward
                while( !m_bStepForward )
                {
                    //for( int ii = 0; ii < 1000; ii++ )
                    //{}
                    System.err.print("");
                }
                
            }
        System.err.println("Received step command");
    }
    void WaitForClock()
    {
        synchronized( m_clock )
        {
            try 
            {
                m_clock.wait();
            } catch (InterruptedException ex) 
            {
                System.out.println( "Exception in MemoryThread.wait : "+ ex );
            }
        }
    }
    
    ///////////////////////////////////////////////////////////
    // Functions for debug execution mode
    ///////////////////////////////////////////////////////////
    public void SetExecutionMode( boolean bDebugMode )
    {     
        m_bDebugMode = bDebugMode;
    }
    public void StepForward()
    {        
        if( m_bDebugMode )
            m_bStepForward = true;
    }
    ///////////////////////////////////////////////////////////
    // Getter functions for private variables for use in the GUI
    ///////////////////////////////////////////////////////////
    int [] GetRegisters()
    {
        int [] retval = new int[4];
        retval[0] = m_nR[0];
        retval[1] = m_nR[1];
        retval[2] = m_nR[2];
        retval[3] = m_nR[3];
        return retval;
    }
    int GetProgramCounter()
    {
        return m_nPC;
    }
    int GetIndexRegister()
    {
        return m_nX0;
    }
    int GetInstructionRegister()
    {
        return m_nIR;
    }
    int GetConditionCode()
    {
        return m_nCC;
    }
    int GetMachineStatusRegister()
    {
        return m_nMSR;
    }
    int GetMachineFaultRegister()
    {
        return m_nMFR;
    }
    
    int GetProcessorState()
    {
        return m_nProcessorState;                
    }
    ///////////////////////////////////////////////////////////
    // The member variables
    ///////////////////////////////////////////////////////////
    private Clock       m_clock;
    private MemoryThread m_Memory;
    private IOThread    m_IO;
    private boolean     m_bHasBootedUp;
    private String      m_sBootupProgramFile;
    
    private boolean     m_bStop;
    // These are the registers available in the processor
    private int         m_nR[];             // The general purpose registers
    private int         m_nPC;              // The program counter
    private int         m_nIR;              // The instruction register
    private int         m_nX0;              // The index register
    
    private int         m_nCC;              // The condition code register
    private int         m_nMSR;             // The MachineStatusRegister
    private int         m_nMFR;             // The MachineFaultRegister


    // These are the internal variables used by the processor to decode and
    // execute instructions
    private int         m_nInstructionFormat; // This defines he format of the instr
                                                // For more details refer to the Assembler
    private int         m_nOpcode;
    private int         m_nR1, m_nR2, m_nOffset, m_nImmediate;
    private int         m_nOperand1, m_nOperand2;
    private boolean     m_bIndirectMode, m_bIndexMode;
    private int         m_nDevId;

    // Stuff required for debug mode
    private boolean     m_bDebugMode;
    private boolean     m_bStepForward;
    
    private int         m_nProcessorState;
    
    
    // These are constants for functioning of processor
    public static int   PS_BOOTING_UP;
    public static int   PS_INSTRUCTION_FETCH;
    public static int   PS_INSTRUCTION_DECODE;
    public static int   PS_INSTRUCTION_EXECUTE;
}
